﻿//
// (C) Copyright 2009 Irantha Suwandarathna (iranthas@hotmail.com)
// All rights reserved.
//

/* Copyright (c) 2001-2008, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use _in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions _in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer _in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using EffiProz.Core.Lib;
using EffiProz.Core.DataTypes;
using EffiProz.Core.Indexes;
using EffiProz.Core.Errors;
using EffiProz.Core.Persist;
using EffiProz.Core.Navigators;

namespace EffiProz.Core
{
    public class ExpressionLogical : Expression
    {
        public bool noOptimisation;

        /**
         * For LIKE
         */
        public ExpressionLogical(int type)
            : base(type)
        {

            dataType = SqlType.SQL_BOOLEAN;
        }

        /**
         * For bool constants
         */
        public ExpressionLogical(bool b)
            : base(OpTypes.VALUE)
        {

            dataType = SqlType.SQL_BOOLEAN;
            valueData = b;
        }

        /*
         * Create an equality expressions using existing columns and
         * range variables. The expression is fully resolved in constructor.
         */
        public ExpressionLogical(RangeVariable leftRangeVar, ColumnSchema left,
                          RangeVariable rightRangeVar, ColumnSchema right)
            : base(OpTypes.EQUAL)
        {


            ExpressionColumn leftExpression = new ExpressionColumn(leftRangeVar,
                left);
            ExpressionColumn rightExpression = new ExpressionColumn(rightRangeVar,
                right);

            nodes = new Expression[BINARY];
            dataType = SqlType.SQL_BOOLEAN;
            nodes[LEFT] = leftExpression;
            nodes[RIGHT] = rightExpression;
        }

        /**
         * Creates an equality expression
         */
        public ExpressionLogical(Expression left, Expression right)
            : base(OpTypes.EQUAL)
        {

            nodes = new Expression[BINARY];
            nodes[LEFT] = left;
            nodes[RIGHT] = right;

            if (left.opType == OpTypes.COLUMN && right.opType == OpTypes.COLUMN)
            {
                isColumnEqual = true;
            }

            dataType = SqlType.SQL_BOOLEAN;
        }

        /**
         * Creates a binary operation expression
         */
        public ExpressionLogical(int type, Expression left, Expression right)
            : base(type)
        {

            nodes = new Expression[BINARY];
            nodes[LEFT] = left;
            nodes[RIGHT] = right;

            switch (opType)
            {

                case OpTypes.EQUAL:
                    if (left.opType == OpTypes.COLUMN
                            && right.opType == OpTypes.COLUMN)
                    {
                        isColumnEqual = true;
                    }
                    goto case OpTypes.NOT_DISTINCT;
                // fall through
                case OpTypes.GREATER_EQUAL:
                case OpTypes.GREATER:
                case OpTypes.SMALLER:
                case OpTypes.SMALLER_EQUAL:
                case OpTypes.NOT_EQUAL:
                case OpTypes.OVERLAPS:
                case OpTypes.NOT_DISTINCT:
                case OpTypes.IN:
                case OpTypes.MATCH_SIMPLE:
                case OpTypes.MATCH_PARTIAL:
                case OpTypes.MATCH_FULL:
                case OpTypes.MATCH_UNIQUE_SIMPLE:
                case OpTypes.MATCH_UNIQUE_PARTIAL:
                case OpTypes.MATCH_UNIQUE_FULL:
                case OpTypes.AND:
                case OpTypes.OR:
                    dataType = SqlType.SQL_BOOLEAN;
                    break;

                default:
                    throw Error.runtimeError(ErrorCode.U_S0500,
                                             "ExpressionLogical");
            }
        }

        /**
         * Creates a unary operation expression
         */
        public ExpressionLogical(int type, Expression e)
            : base(type)
        {


            nodes = new Expression[UNARY];
            nodes[LEFT] = e;

            switch (opType)
            {

                case OpTypes.UNIQUE:
                case OpTypes.EXISTS:
                case OpTypes.IS_NULL:
                case OpTypes.NOT:
                    dataType = SqlType.SQL_BOOLEAN;
                    break;

                default:
                    throw Error.runtimeError(ErrorCode.U_S0500,
                                             "ExpressionLogical");
            }
        }

        /**
         * Creates a column not null expression
         */
        public ExpressionLogical(ColumnSchema column)
            : base(OpTypes.NOT)
        {

            nodes = new Expression[UNARY];
            dataType = SqlType.SQL_BOOLEAN;

            Expression e = new ExpressionColumn(column);

            e = new ExpressionLogical(OpTypes.IS_NULL, e);
            nodes[LEFT] = e;
        }

        // logical ops
        public static Expression andExpressions(Expression e1, Expression e2)
        {

            if (e1 == null)
            {
                return e2;
            }

            if (e2 == null)
            {
                return e1;
            }

            return new ExpressionLogical(OpTypes.AND, e1, e2);
        }

        public override String getSQL()
        {

            StringBuilder sb = new StringBuilder(64);

            if (opType == OpTypes.VALUE)
            {
                return base.getSQL();
            }

            String left = getContextSQL(nodes[LEFT]);
            String right = getContextSQL(nodes.Length > 1 ? nodes[RIGHT]
                                                          : null);

            switch (opType)
            {

                case OpTypes.NOT:
                    if (nodes[LEFT].opType == OpTypes.IS_NULL)
                    {
                        sb.Append(getContextSQL(nodes[LEFT].nodes[LEFT])).Append(
                            ' ').Append(Tokens.T_IS).Append(' ').Append(
                            Tokens.T_NOT).Append(' ').Append(Tokens.T_NULL);

                        return sb.ToString();
                    }

                    if (nodes[LEFT].opType == OpTypes.NOT_DISTINCT)
                    {
                        sb.Append(getContextSQL(nodes[LEFT].nodes[LEFT])).Append(
                            ' ').Append(Tokens.T_IS).Append(' ').Append(
                            Tokens.T_DISTINCT).Append(' ').Append(
                            Tokens.T_FROM).Append(' ').Append(
                            getContextSQL(nodes[LEFT].nodes[RIGHT]));

                        return sb.ToString();
                    }

                    sb.Append(Tokens.T_NOT).Append(' ').Append(left);

                    return sb.ToString();

                case OpTypes.NOT_DISTINCT:
                    sb.Append(Tokens.T_NOT).Append(' ').Append(
                        getContextSQL(nodes[LEFT].nodes[LEFT])).Append(' ').Append(
                        Tokens.T_IS).Append(' ').Append(Tokens.T_DISTINCT).Append(
                        ' ').Append(Tokens.T_FROM).Append(' ').Append(
                        getContextSQL(nodes[LEFT].nodes[RIGHT]));

                    return sb.ToString();

                case OpTypes.IS_NULL:
                    sb.Append(left).Append(' ').Append(Tokens.T_IS).Append(
                        ' ').Append(Tokens.T_NULL);

                    return sb.ToString();

                case OpTypes.UNIQUE:
                    sb.Append(' ').Append(Tokens.T_UNIQUE).Append(' ');
                    break;

                case OpTypes.EXISTS:
                    sb.Append(' ').Append(Tokens.T_EXISTS).Append(' ');
                    break;

                case OpTypes.EQUAL:
                    sb.Append(left).Append('=').Append(right);

                    return sb.ToString();

                case OpTypes.GREATER_EQUAL:
                    sb.Append(left).Append(">=").Append(right);

                    return sb.ToString();

                case OpTypes.GREATER:
                    sb.Append(left).Append('>').Append(right);

                    return sb.ToString();

                case OpTypes.SMALLER:
                    sb.Append(left).Append('<').Append(right);

                    return sb.ToString();

                case OpTypes.SMALLER_EQUAL:
                    sb.Append(left).Append("<=").Append(right);

                    return sb.ToString();

                case OpTypes.NOT_EQUAL:
                    if (Tokens.T_NULL.Equals(right))
                    {
                        sb.Append(left).Append(" IS NOT ").Append(right);
                    }
                    else
                    {
                        sb.Append(left).Append("!=").Append(right);
                    }

                    return sb.ToString();

                case OpTypes.AND:
                    sb.Append(left).Append(' ').Append(Tokens.T_AND).Append(
                        ' ').Append(right);

                    return sb.ToString();

                case OpTypes.OR:
                    sb.Append(left).Append(' ').Append(Tokens.T_OR).Append(
                        ' ').Append(right);

                    return sb.ToString();

                case OpTypes.IN:
                    sb.Append(left).Append(' ').Append(Tokens.T_IN).Append(
                        ' ').Append(right);

                    return sb.ToString();

                case OpTypes.MATCH_SIMPLE:
                    sb.Append(left).Append(' ').Append(Tokens.T_MATCH).Append(
                        ' ').Append(right);

                    return sb.ToString();

                case OpTypes.MATCH_PARTIAL:
                    sb.Append(left).Append(' ').Append(Tokens.T_MATCH).Append(
                        ' ').Append(Tokens.PARTIAL).Append(right);

                    return sb.ToString();

                case OpTypes.MATCH_FULL:
                    sb.Append(left).Append(' ').Append(Tokens.T_MATCH).Append(
                        ' ').Append(Tokens.FULL).Append(right);

                    return sb.ToString();

                case OpTypes.MATCH_UNIQUE_SIMPLE:
                    sb.Append(left).Append(' ').Append(Tokens.T_MATCH).Append(
                        ' ').Append(Tokens.UNIQUE).Append(right);

                    return sb.ToString();

                case OpTypes.MATCH_UNIQUE_PARTIAL:
                    sb.Append(left).Append(' ').Append(Tokens.T_MATCH).Append(
                        ' ').Append(Tokens.UNIQUE).Append(' ').Append(
                        Tokens.PARTIAL).Append(right);

                    return sb.ToString();

                case OpTypes.MATCH_UNIQUE_FULL:
                    sb.Append(left).Append(' ').Append(Tokens.T_MATCH).Append(
                        ' ').Append(Tokens.UNIQUE).Append(' ').Append(
                        Tokens.FULL).Append(right);

                    return sb.ToString();

                default:
                    throw Error.runtimeError(ErrorCode.U_S0500,
                                             "ExpressionLogical");
            }

            return sb.ToString();
        }

        public override String describe(Session session, int blanks)
        {

            StringBuilder sb = new StringBuilder(64);

            sb.Append('\n');

            for (int i = 0; i < blanks; i++)
            {
                sb.Append(' ');
            }

            switch (opType)
            {

                case OpTypes.VALUE:
                    sb.Append("VALUE = ").Append(valueData);
                    sb.Append(", TYPE = ").Append(dataType.getNameString());

                    return sb.ToString();

                case OpTypes.NOT:
                    if (nodes[LEFT].opType == OpTypes.NOT_DISTINCT)
                    {
                        sb.Append(Tokens.T_DISTINCT);

                        return sb.ToString();
                    }

                    sb.Append("NOT ");
                    break;

                case OpTypes.NOT_DISTINCT:
                    sb.Append("NOT ");
                    sb.Append("DISTINCT ");
                    break;

                case OpTypes.EQUAL:
                    sb.Append("EQUAL ");
                    break;

                case OpTypes.GREATER_EQUAL:
                    sb.Append("GREATER_EQUAL ");
                    break;

                case OpTypes.GREATER:
                    sb.Append("GREATER ");
                    break;

                case OpTypes.SMALLER:
                    sb.Append("SMALLER ");
                    break;

                case OpTypes.SMALLER_EQUAL:
                    sb.Append("SMALLER_EQUAL ");
                    break;

                case OpTypes.NOT_EQUAL:
                    sb.Append("NOT_EQUAL ");
                    break;

                case OpTypes.AND:
                    sb.Append("AND ");
                    break;

                case OpTypes.OR:
                    sb.Append("OR ");
                    break;

                case OpTypes.MATCH_SIMPLE:
                case OpTypes.MATCH_PARTIAL:
                case OpTypes.MATCH_FULL:
                case OpTypes.MATCH_UNIQUE_SIMPLE:
                case OpTypes.MATCH_UNIQUE_PARTIAL:
                case OpTypes.MATCH_UNIQUE_FULL:
                    sb.Append("MATCH ");
                    break;

                case OpTypes.IS_NULL:
                    sb.Append("IS_NULL ");
                    break;

                case OpTypes.UNIQUE:
                    sb.Append("UNIQUE ");
                    break;

                case OpTypes.EXISTS:
                    sb.Append("EXISTS ");
                    break;

                default:
                    throw Error.runtimeError(ErrorCode.U_S0500,
                                             "ExpressionLogical");
            }

            if (nodes[LEFT] != null)
            {
                sb.Append(" arg_left=[");
                sb.Append(nodes[LEFT].describe(session, blanks + 1));
                sb.Append(']');
            }

            if (nodes[RIGHT] != null)
            {
                sb.Append(" arg_right=[");
                sb.Append(nodes[RIGHT].describe(session, blanks + 1));
                sb.Append(']');
            }

            return sb.ToString();
        }

        public override void resolveTypes(Session session, Expression parent)
        {

            for (int i = 0; i < nodes.Length; i++)
            {
                if (nodes[i] != null)
                {
                    nodes[i].resolveTypes(session, this);
                }
            }

            switch (opType)
            {

                case OpTypes.VALUE:
                    break;

                case OpTypes.NOT_DISTINCT:
                case OpTypes.EQUAL:
                case OpTypes.GREATER_EQUAL:
                case OpTypes.GREATER:
                case OpTypes.SMALLER:
                case OpTypes.SMALLER_EQUAL:
                case OpTypes.NOT_EQUAL:
                    resolveTypesForComparison(session, parent);
                    break;

                case OpTypes.AND:
                    {
                        resolveTypesForLogicalOp();

                        if (nodes[LEFT].opType == OpTypes.VALUE)
                        {
                            if (nodes[RIGHT].opType == OpTypes.VALUE)
                            {
                                setAsConstantValue(session);
                            }
                            else
                            {
                                Object value = nodes[LEFT].getValue(session);

                                if (value == null || false.Equals(value))
                                {
                                    setAsConstantValue(false);
                                }
                            }
                        }
                        else if (nodes[RIGHT].opType == OpTypes.VALUE)
                        {
                            Object value = nodes[RIGHT].getValue(session);

                            if (value == null || false.Equals(value))
                            {
                                setAsConstantValue(false);
                            }
                        }

                        break;
                    }
                case OpTypes.OR:
                    {
                        resolveTypesForLogicalOp();

                        if (nodes[LEFT].opType == OpTypes.VALUE)
                        {
                            if (nodes[RIGHT].opType == OpTypes.VALUE)
                            {
                                setAsConstantValue(session);
                            }
                            else
                            {
                                Object value = nodes[LEFT].getValue(session);

                                if (true.Equals(value))
                                {
                                    setAsConstantValue(true);
                                }
                            }
                        }
                        else if (nodes[RIGHT].opType == OpTypes.VALUE)
                        {
                            Object value = nodes[RIGHT].getValue(session);

                            if (true.Equals(value))
                            {
                                setAsConstantValue(true);
                            }
                        }

                        break;
                    }
                case OpTypes.IS_NULL:
                    if (nodes[LEFT]._isParam)
                    {
                        throw Error.error(ErrorCode.X_42565);
                    }

                    if (nodes[LEFT].opType == OpTypes.VALUE)
                    {
                        setAsConstantValue(session);
                    }
                    break;

                case OpTypes.NOT:
                    if (nodes[LEFT]._isParam)
                    {
                        nodes[LEFT].dataType = SqlType.SQL_BOOLEAN;

                        break;
                    }

                    if (nodes[LEFT].opType == OpTypes.VALUE)
                    {
                        if (nodes[LEFT].dataType.isBooleanType())
                        {
                            setAsConstantValue(session);

                            break;
                        }
                        else
                        {
                            throw Error.error(ErrorCode.X_42565);
                        }
                    }

                    if (nodes[LEFT].dataType == null
                            || !nodes[LEFT].dataType.isBooleanType())
                    {
                        throw Error.error(ErrorCode.X_42565);
                    }

                    dataType = SqlType.SQL_BOOLEAN;
                    break;

                case OpTypes.OVERLAPS:
                    resolveTypesForOverlaps();
                    break;

                case OpTypes.IN:
                    resolveTypesForIn(session);
                    break;

                case OpTypes.MATCH_SIMPLE:
                case OpTypes.MATCH_PARTIAL:
                case OpTypes.MATCH_FULL:
                case OpTypes.MATCH_UNIQUE_SIMPLE:
                case OpTypes.MATCH_UNIQUE_PARTIAL:
                case OpTypes.MATCH_UNIQUE_FULL:
                    resolveTypesForAllAny(session);
                    break;

                case OpTypes.UNIQUE:
                case OpTypes.EXISTS:
                    break;

                default:
                    throw Error.runtimeError(ErrorCode.U_S0500,
                                             "ExpressionLogical");
            }
        }

        private void resolveTypesForLogicalOp()
        {

            if (nodes[LEFT]._isParam)
            {
                nodes[LEFT].dataType = SqlType.SQL_BOOLEAN;
            }

            if (nodes[RIGHT]._isParam)
            {
                nodes[RIGHT].dataType = SqlType.SQL_BOOLEAN;
            }

            if (nodes[LEFT].dataType == null || nodes[RIGHT].dataType == null)
            {
                throw Error.error(ErrorCode.X_42571);
            }

            if (nodes[LEFT].opType == OpTypes.ROW
                    || nodes[RIGHT].opType == OpTypes.ROW
                    || SqlType.SQL_BOOLEAN != nodes[LEFT].dataType
                    || SqlType.SQL_BOOLEAN != nodes[RIGHT].dataType)
            {
                throw Error.error(ErrorCode.X_42564);
            }
        }

        private void resolveTypesForComparison(Session session,
                                               Expression parent)
        {

            if (exprSubType == OpTypes.ALL_QUANTIFIED
                    || exprSubType == OpTypes.ANY_QUANTIFIED)
            {
                resolveTypesForAllAny(session);

                return;
            }

            if (nodes[LEFT].opType == OpTypes.ROW
                    || nodes[RIGHT].opType == OpTypes.ROW)
            {
                if (nodes[LEFT].opType != OpTypes.ROW
                        || nodes[RIGHT].opType != OpTypes.ROW
                        || nodes[LEFT].nodes.Length != nodes[RIGHT].nodes.Length)
                {
                    throw Error.error(ErrorCode.X_42564);
                }

                resolveRowTypes();
            }
            else
            {
                if (nodes[LEFT]._isParam)
                {
                    nodes[LEFT].dataType = nodes[RIGHT].dataType;
                }
                else if (nodes[RIGHT]._isParam)
                {
                    nodes[RIGHT].dataType = nodes[LEFT].dataType;
                }

                if (nodes[LEFT].dataType == null
                        || nodes[RIGHT].dataType == null)
                {
                    throw Error.error(ErrorCode.X_42567);
                }

                if (nodes[LEFT].dataType.typeComparisonGroup
                        != nodes[RIGHT].dataType.typeComparisonGroup)
                {
                    if (convertDateTimeLiteral(session, nodes[LEFT],
                                               nodes[RIGHT]))
                    {

                        // compatibility for BIT with number and BOOLEAN - convert bit to other type
                    }
                    else if (nodes[LEFT].dataType.isBitType())
                    {
                        if (nodes[RIGHT].dataType.canConvertFrom(
                                nodes[LEFT].dataType))
                        {
                            nodes[LEFT] = ExpressionOp.getCastExpression(session,
                                    nodes[LEFT], nodes[RIGHT].dataType);
                        }
                    }
                    else if (nodes[RIGHT].dataType.isBitType())
                    {
                        if (nodes[LEFT].dataType.canConvertFrom(
                                nodes[RIGHT].dataType))
                        {
                            nodes[RIGHT] = ExpressionOp.getCastExpression(session,
                                    nodes[RIGHT], nodes[LEFT].dataType);
                        }
                    }
                    else
                    {
                        throw Error.error(ErrorCode.X_42562);
                    }
                }
                else if (nodes[LEFT].dataType.isDateTimeType())
                {
                    if (nodes[LEFT].dataType.isDateTimeTypeWithZone()
                            ^ nodes[RIGHT].dataType.isDateTimeTypeWithZone())
                    {
                        nodes[LEFT] = new ExpressionOp(nodes[LEFT]);
                    }
                }
            }

            if (nodes[LEFT].opType == OpTypes.VALUE
                    && nodes[RIGHT].opType == OpTypes.VALUE)
            {
                setAsConstantValue(session);
            }
        }

        private void resolveRowTypes()
        {

            for (int i = 0; i < nodes[LEFT].nodeDataTypes.Length; i++)
            {
                SqlType leftType = nodes[LEFT].nodeDataTypes[i];
                SqlType rightType = nodes[RIGHT].nodeDataTypes[i];

                if (leftType.typeComparisonGroup
                        != rightType.typeComparisonGroup)
                {
                    throw Error.error(ErrorCode.X_42562);
                }
                else if (leftType.isDateTimeType())
                {
                    if (leftType.isDateTimeTypeWithZone()
                            ^ rightType.isDateTimeTypeWithZone())
                    {
                        nodes[LEFT].nodes[i] =
                            new ExpressionOp(nodes[LEFT].nodes[i]);
                        nodes[LEFT].nodeDataTypes[i] =
                            nodes[LEFT].nodes[i].dataType;
                    }
                }
            }
        }

        /**
         * for compatibility, convert a datetime character string to a datetime
         * value for comparison
         */
        private bool convertDateTimeLiteral(Session session, Expression a,
                                               Expression b)
        {

            if (a.dataType.isDateTimeType()) { }
            else if (b.dataType.isDateTimeType())
            {
                Expression c = a;

                a = b;
                b = c;
            }
            else
            {
                return false;
            }

            if (a.dataType.isDateTimeTypeWithZone())
            {
                return false;
            }

            if (b.opType == OpTypes.VALUE && b.dataType.isCharacterType())
            {
                b.valueData = a.dataType.castToType(session, b.valueData,
                                                    b.dataType);
                b.dataType = a.dataType;

                return true;
            }

            return false;
        }

        void resolveTypesForOverlaps()
        {

            if (nodes[LEFT].nodes[0]._isParam)
            {
                nodes[LEFT].nodes[0].dataType = nodes[RIGHT].nodes[0].dataType;
            }

            if (nodes[RIGHT].nodes[0]._isParam)
            {
                nodes[RIGHT].nodes[0].dataType = nodes[LEFT].nodes[0].dataType;
            }

            if (nodes[LEFT].nodes[0].dataType == null)
            {
                nodes[LEFT].nodes[0].dataType = nodes[RIGHT].nodes[0].dataType =
                    SqlType.SQL_TIMESTAMP;
            }

            if (nodes[LEFT].nodes[1]._isParam)
            {
                nodes[LEFT].nodes[1].dataType = nodes[RIGHT].nodes[0].dataType;
            }

            if (nodes[RIGHT].nodes[1]._isParam)
            {
                nodes[RIGHT].nodes[1].dataType = nodes[LEFT].nodes[0].dataType;
            }

            if (!DTIType
                    .isValidDatetimeRange(nodes[LEFT].nodes[0]
                        .dataType, nodes[LEFT].nodes[1].dataType) || !DTIType
                            .isValidDatetimeRange(nodes[RIGHT].nodes[0]
                                .dataType, nodes[RIGHT].nodes[1].dataType))
            {
                throw Error.error(ErrorCode.X_42565);
            }

            if (!DTIType.isValidDatetimeRange(nodes[LEFT].nodes[0].dataType,
                                              nodes[LEFT].nodes[1].dataType))
            {
                throw Error.error(ErrorCode.X_42563);
            }

            nodes[LEFT].nodeDataTypes[0] = nodes[LEFT].nodes[0].dataType;
            nodes[LEFT].nodeDataTypes[1] = nodes[LEFT].nodes[1].dataType;
            nodes[RIGHT].nodeDataTypes[0] = nodes[RIGHT].nodes[0].dataType;
            nodes[RIGHT].nodeDataTypes[1] = nodes[RIGHT].nodes[1].dataType;
        }

        void resolveTypesForAllAny(Session session)
        {

            int degree = nodes[LEFT].getDegree();

            if (degree == 1 && nodes[LEFT].opType != OpTypes.ROW)
            {
                nodes[LEFT] = new Expression(OpTypes.ROW,
                                             new Expression[] { nodes[LEFT] });
            }

            if (nodes[RIGHT].opType == OpTypes.TABLE)
            {
                nodes[RIGHT].prepareTable(session, nodes[LEFT], degree);
                nodes[RIGHT].subQuery.prepareTable(session);

                if (nodes[RIGHT]._isCorrelated)
                {
                    nodes[RIGHT].subQuery.setCorrelated();
                }
            }

            if (degree != nodes[RIGHT].nodeDataTypes.Length)
            {
                throw Error.error(ErrorCode.X_42564);
            }

            if (nodes[RIGHT].opType == OpTypes.TABLE) { }

            nodes[LEFT].nodeDataTypes = new SqlType[nodes[LEFT].nodes.Length];

            for (int i = 0; i < nodes[LEFT].nodeDataTypes.Length; i++)
            {
                SqlType type = nodes[LEFT].nodes[i].dataType;

                if (type == null)
                {
                    type = nodes[RIGHT].nodeDataTypes[i];
                }

                if (type == null)
                {
                    throw Error.error(ErrorCode.X_42567);
                }

                nodes[LEFT].nodeDataTypes[i] = type;
            }
        }

        public void resolveTypesForIn(Session session)
        {
            resolveTypesForAllAny(session);
        }

        public override Object getValue(Session session)
        {

            switch (opType)
            {

                case OpTypes.VALUE:
                    return valueData;

                case OpTypes.SIMPLE_COLUMN:
                    {
                        Object[] data =
                            (Object[])session.sessionContext
                                .rangeIterators[rangePosition].getCurrent();

                        return data[columnIndex];
                    }
                case OpTypes.NEGATE:
                    return ((NumberType)dataType).negate(
                        nodes[LEFT].getValue(session, nodes[LEFT].dataType));

                case OpTypes.IS_NULL:
                    return nodes[LEFT].getValue(session) == null ? true
                                                                 : false;

                case OpTypes.OVERLAPS:
                    {
                        Object[] left = nodes[LEFT].getRowValue(session);
                        Object[] right = nodes[RIGHT].getRowValue(session);

                        return DateTimeType.overlaps(session, left,
                                                     nodes[LEFT].nodeDataTypes, right,
                                                     nodes[RIGHT].nodeDataTypes);
                    }
                case OpTypes.IN:
                    {
                        return testInCondition(session,
                                               nodes[LEFT].getRowValue(session));
                    }
                case OpTypes.MATCH_SIMPLE:
                case OpTypes.MATCH_PARTIAL:
                case OpTypes.MATCH_FULL:
                case OpTypes.MATCH_UNIQUE_SIMPLE:
                case OpTypes.MATCH_UNIQUE_PARTIAL:
                case OpTypes.MATCH_UNIQUE_FULL:
                    {
                        return testMatchCondition(session,
                                                  nodes[LEFT].getRowValue(session));
                    }
                case OpTypes.UNIQUE:
                    {
                        nodes[LEFT].subQuery.materialiseCorrelated(session);

                        return nodes[LEFT].subQuery.hasUniqueNotNullRows(session)
                               ? true
                               : false;
                    }
                case OpTypes.EXISTS:
                    {
                        return testExistsCondition(session);
                    }
                case OpTypes.NOT:
                    {
                        Boolean? result = (Boolean?)nodes[LEFT].getValue(session);

                        return result == null ? null
                                              : (bool?)((bool)result ? false : true);
                    }
                case OpTypes.AND:
                    {
                        Boolean? r1 = (Boolean?)nodes[LEFT].getValue(session);

                        if (false.Equals(r1))
                        {
                            return false;
                        }

                        Boolean? r2 = (Boolean?)nodes[RIGHT].getValue(session);

                        if (false.Equals(r2))
                        {
                            return false;
                        }

                        if (r1 == null || r2 == null)
                        {
                            return null;
                        }

                        return true;
                    }
                case OpTypes.OR:
                    {
                        Boolean? r1 = (Boolean?)nodes[LEFT].getValue(session);

                        if (true.Equals(r1))
                        {
                            return true;
                        }

                        Boolean? r2 = (Boolean?)nodes[RIGHT].getValue(session);

                        if (true.Equals(r2))
                        {
                            return true;
                        }

                        if (r1 == null || r2 == null)
                        {
                            return null;
                        }

                        return false;
                    }
                case OpTypes.NOT_DISTINCT:
                case OpTypes.EQUAL:
                case OpTypes.GREATER:
                case OpTypes.GREATER_EQUAL:
                case OpTypes.SMALLER_EQUAL:
                case OpTypes.SMALLER:
                case OpTypes.NOT_EQUAL:
                    {
                        if (exprSubType == OpTypes.ANY_QUANTIFIED
                                || exprSubType == OpTypes.ALL_QUANTIFIED)
                        {
                            return testAllAnyCondition(
                                session, (Object[])nodes[LEFT].getRowValue(session));
                        }

                        Object o1 = nodes[LEFT].getValue(session);
                        Object o2 = nodes[RIGHT].getValue(session);

                        if (o1 is Object[])
                        {
                            return compareValues(session, (Object[])o1,
                                                 (Object[])o2);
                        }
                        else
                        {
                            return compareValues(session, o1, o2);
                        }
                    }
                default:
                    throw Error.runtimeError(ErrorCode.U_S0500,
                                             "ExpressionLogical");
            }
        }

        /**
         * For MATCH SIMPLE and FULL expressions, nulls in left are handled
         * prior to calling this method
         */
        private bool? compareValues(Session session, Object left, Object right)
        {

            int result = 0;

            if (left == null || right == null)
            {
                return null;
            }

            result = nodes[LEFT].dataType.compare(session, left, right);

            switch (opType)
            {

                case OpTypes.EQUAL:
                    return result == 0 ? true
                                       : false;

                case OpTypes.NOT_EQUAL:
                    return result != 0 ? true
                                       : false;

                case OpTypes.GREATER:
                    return result > 0 ? true
                                      : false;

                case OpTypes.GREATER_EQUAL:
                    return result >= 0 ? true
                                       : false;

                case OpTypes.SMALLER_EQUAL:
                    return result <= 0 ? true
                                       : false;

                case OpTypes.SMALLER:
                    return result < 0 ? true
                                      : false;

                default:
                    throw Error.runtimeError(ErrorCode.U_S0500,
                                             "ExpressionLogical");
            }
        }

        /**
         * For MATCH SIMPLE and FULL expressions, nulls in left are handled
         * prior to calling this method
         */
        private bool? compareValues(Session session, Object[] left,
                                      Object[] right)
        {

            int result = 0;
            bool hasNull = false;

            if (left == null || right == null)
            {
                return null;
            }

            Object[] leftList = (Object[])left;
            Object[] rightList = (Object[])right;

            for (int i = 0; i < nodes[LEFT].nodes.Length; i++)
            {
                if (leftList[i] == null)
                {
                    if (opType == OpTypes.MATCH_PARTIAL
                            || opType == OpTypes.MATCH_UNIQUE_PARTIAL)
                    {
                        continue;
                    }

                    hasNull = true;
                }

                if (rightList[i] == null)
                {
                    hasNull = true;
                }

                Object leftValue = leftList[i];
                Object rightValue = rightList[i];
                SqlType[] types = nodes[LEFT].nodeDataTypes;

                result = types[i].compare(session, leftValue, rightValue);

                if (result != 0)
                {
                    break;
                }
            }

            switch (opType)
            {

                case OpTypes.MATCH_SIMPLE:
                case OpTypes.MATCH_UNIQUE_SIMPLE:
                case OpTypes.MATCH_PARTIAL:
                case OpTypes.MATCH_UNIQUE_PARTIAL:
                case OpTypes.MATCH_FULL:
                case OpTypes.MATCH_UNIQUE_FULL:
                case OpTypes.NOT_DISTINCT:
                    return result == 0 ? true
                                       : false;

                case OpTypes.IN:
                case OpTypes.EQUAL:
                    if (hasNull)
                    {
                        return null;
                    }

                    return result == 0 ? true
                                       : false;

                case OpTypes.NOT_EQUAL:
                    if (hasNull)
                    {
                        return null;
                    }

                    return result != 0 ? true
                                       : false;

                case OpTypes.GREATER:
                    if (hasNull)
                    {
                        return null;
                    }

                    return result > 0 ? true
                                      : false;

                case OpTypes.GREATER_EQUAL:
                    if (hasNull)
                    {
                        return null;
                    }

                    return result >= 0 ? true
                                       : false;

                case OpTypes.SMALLER_EQUAL:
                    if (hasNull)
                    {
                        return null;
                    }

                    return result <= 0 ? true
                                       : false;

                case OpTypes.SMALLER:
                    if (hasNull)
                    {
                        return null;
                    }

                    return result < 0 ? true
                                      : false;

                default:
                    throw Error.runtimeError(ErrorCode.U_S0500,
                                             "ExpressionLogical");
            }
        }

        /**
         * Returns the result of testing a VALUE_LIST expression
         */
        private bool? testInCondition(Session session, Object[] data)
        {

            if (data == null)
            {
                return null;
            }

            if (Expression.countNulls(data) != 0)
            {
                return null;
            }

            if (nodes[RIGHT].opType == OpTypes.TABLE)
            {
                int length = nodes[RIGHT].nodes.Length;

                for (int i = 0; i < length; i++)
                {
                    Object[] rowData = nodes[RIGHT].nodes[i].getRowValue(session);

                    if (true.Equals(compareValues(session, data,
                                                          rowData)))
                    {
                        return true;
                    }
                }

                return false;
            }

            throw Error.runtimeError(ErrorCode.U_S0500, "ExpressionLogical");
        }

        private Boolean testMatchCondition(Session session, Object[] data)
        {

            int nulls;

            if (data == null)
            {
                return true;
            }

            nulls = countNulls(data);

            if (nulls != 0)
            {
                switch (opType)
                {

                    case OpTypes.MATCH_SIMPLE:
                    case OpTypes.MATCH_UNIQUE_SIMPLE:
                        return true;

                    case OpTypes.MATCH_PARTIAL:
                    case OpTypes.MATCH_UNIQUE_PARTIAL:
                        if (nulls == data.Length)
                        {
                            return true;
                        }
                        break;

                    case OpTypes.MATCH_FULL:
                    case OpTypes.MATCH_UNIQUE_FULL:
                        return nulls == data.Length ? true
                                                    : false;
                }
            }

            if (nodes[RIGHT].opType == OpTypes.TABLE)
            {
                int length = nodes[RIGHT].nodes.Length;
                bool hasMatch = false;

                for (int i = 0; i < length; i++)
                {
                    Object[] rowData = nodes[RIGHT].nodes[i].getRowValue(session);
                    Boolean? result = compareValues(session, data, rowData);

                    if (result == null || !(bool)result)
                    {
                        continue;
                    }

                    switch (opType)
                    {

                        case OpTypes.MATCH_SIMPLE:
                        case OpTypes.MATCH_PARTIAL:
                        case OpTypes.MATCH_FULL:
                            return true;

                        case OpTypes.MATCH_UNIQUE_SIMPLE:
                        case OpTypes.MATCH_UNIQUE_PARTIAL:
                        case OpTypes.MATCH_UNIQUE_FULL:
                            if (hasMatch)
                            {
                                return false;
                            }

                            hasMatch = true;
                            break;
                    }
                }

                return hasMatch ? true
                                : false;
            }
            else if (nodes[RIGHT].opType == OpTypes.TABLE_SUBQUERY)
            {
                PersistentStore store = session.sessionData.getRowStore(
                    nodes[RIGHT].subQuery.getTable());

                nodes[RIGHT].subQuery.materialiseCorrelated(session);
                convertToType(session, data, nodes[LEFT].nodeDataTypes,
                              nodes[RIGHT].nodeDataTypes);

                if (nulls != 0
                        && (opType == OpTypes.MATCH_PARTIAL
                            || opType == OpTypes.MATCH_UNIQUE_PARTIAL))
                {
                    bool hasMatch = false;
                    RowIterator it =
                        nodes[RIGHT].subQuery.getTable().rowIterator(session);

                    while (it.hasNext())
                    {
                        Object[] rowData = it.getNextRow().getData();
                        Boolean? result = compareValues(session, data, rowData);

                        if (result == null)
                        {
                            continue;
                        }

                        if ((bool)result)
                        {
                            if (opType == OpTypes.MATCH_PARTIAL)
                            {
                                return true;
                            }

                            if (hasMatch)
                            {
                                return false;
                            }

                            hasMatch = true;
                        }
                    }

                    return hasMatch ? true
                                    : false;
                }

                RowIterator _it =
                    nodes[RIGHT].subQuery.getTable().getPrimaryIndex()
                        .findFirstRow(session, store, data);
                bool _result = _it.hasNext();

                if (!_result)
                {
                    return false;
                }

                switch (opType)
                {

                    case OpTypes.MATCH_SIMPLE:
                    case OpTypes.MATCH_PARTIAL:
                    case OpTypes.MATCH_FULL:
                        return true;
                }

                _it.getNextRow();

                _result = _it.hasNext();

                if (!_result)
                {
                    return true;
                }

                Object[] _rowData = _it.getNextRow().getData();
                Boolean returnValue =
                    true.Equals(compareValues(session, data, _rowData))
                    ? false
                    : true;

                return returnValue;
            }

            throw Error.error(ErrorCode.X_42564);
        }

        private Boolean testExistsCondition(Session session)
        {

            SubQuery subQuery = nodes[LEFT].subQuery;

            if (subQuery.isCorrelated())
            {
                subQuery.materialiseCorrelated(session);
                /*
                        Result r = subQuery.queryExpression.getResult(session, 1);    // 1 is already enough

                        return r.getNavigator().isEmpty() ? false
                                                          : true;
            */
            }

            return subQuery.getTable().isEmpty(session) ? false
                                                        : true;
        }

        private bool? testAllAnyCondition(Session session, Object[] o)
        {

            SubQuery subquery = nodes[RIGHT].subQuery;

            subquery.materialiseCorrelated(session);

            bool? result = getAllAnyValue(session, o, subquery);

            return result;
        }

        /** @todo - null value in rows */
        private Boolean? getAllAnyValue(Session session, Object[] data,
                                       SubQuery subquery)
        {

            Table table = subquery.getTable();
            bool empty = table.isEmpty(session);
            Index index = table.getFullIndex();
            RowIterator it;
            Row firstrow;
            PersistentStore store = session.sessionData.getRowStore(table);
            Row lastrow = index.lastRow(session, store);
            Object[] lastdata;
            Object[] firstdata;

            switch (exprSubType)
            {

                case OpTypes.ANY_QUANTIFIED:
                    {
                        if (empty)
                        {
                            return false;
                        }

                        if (countNulls(data) == data.Length)
                        {
                            return null;
                        }

                        lastdata = lastrow.getData();

                        if (countNulls(lastdata) == data.Length)
                        {
                            return null;
                        }

                        convertToType(session, data, nodes[LEFT].nodeDataTypes,
                                      nodes[RIGHT].nodeDataTypes);

                        if (opType == OpTypes.EQUAL)
                        {
                            it = index.findFirstRow(session, store, data);

                            return it.hasNext() ? true
                                                : false;
                        }

                        it = index.findFirstRowNotNull(session, store);
                        firstrow = it.getNextRow();
                        firstdata = firstrow.getData();

                        Boolean? comparefirst = compareValues(session, data, firstdata);
                        Boolean? comparelast = compareValues(session, data, lastdata);

                        switch (opType)
                        {

                            case OpTypes.NOT_EQUAL:
                                return true.Equals(comparefirst)
                                       || true.Equals(
                                           comparelast) ? true
                                                        : false;

                            case OpTypes.GREATER:
                                return comparefirst;

                            case OpTypes.GREATER_EQUAL:
                                return comparefirst;

                            case OpTypes.SMALLER:
                                return comparelast;

                            case OpTypes.SMALLER_EQUAL:
                                return comparelast;
                        }

                        break;
                    }
                case OpTypes.ALL_QUANTIFIED:
                    {
                        if (empty)
                        {
                            return true;
                        }

                        if (countNulls(data) == data.Length)
                        {
                            return null;
                        }

                        it = index.firstRow(session, store);
                        firstrow = it.getNextRow();
                        firstdata = firstrow.getData();

                        if (countNulls(firstdata) == data.Length)
                        {
                            return null;
                        }

                        convertToType(session, data, nodes[LEFT].nodeDataTypes,
                                      nodes[RIGHT].nodeDataTypes);

                        it = index.findFirstRow(session, store, data);

                        if (opType == OpTypes.EQUAL)
                        {
                            if (it.hasNext())
                            {
                                return subquery.getTable().getRowCount(store) == 1
                                       ? true
                                       : false;
                            }
                            else
                            {
                                return false;
                            }
                        }

                        if (opType == OpTypes.NOT_EQUAL)
                        {
                            return it.hasNext() ? false
                                                : true;
                        }

                        lastdata = lastrow.getData();

                        Boolean? comparefirst = compareValues(session, data, firstdata);
                        Boolean? comparelast = compareValues(session, data, lastdata);

                        switch (opType)
                        {

                            case OpTypes.GREATER:
                                return comparelast;

                            case OpTypes.GREATER_EQUAL:
                                return comparelast;

                            case OpTypes.SMALLER:
                                return comparefirst;

                            case OpTypes.SMALLER_EQUAL:
                                return comparefirst;
                        }

                        break;
                    }
            }

            return null;
        }

        /**
         * Converts an OR containing an AND to an AND
         */
        public void distributeOr()
        {

            if (opType != OpTypes.OR)
            {
                return;
            }

            if (nodes[LEFT].opType == OpTypes.AND)
            {
                opType = OpTypes.AND;

                Expression temp = new ExpressionLogical(OpTypes.OR,
                    nodes[LEFT].nodes[RIGHT], nodes[RIGHT]);

                nodes[LEFT].opType = OpTypes.OR;
                nodes[LEFT].nodes[RIGHT] = nodes[RIGHT];
                nodes[RIGHT] = temp;
            }
            else if (nodes[RIGHT].opType == OpTypes.AND)
            {
                Expression temp = nodes[LEFT];

                nodes[LEFT] = nodes[RIGHT];
                nodes[RIGHT] = temp;

                distributeOr();

                return;
            }

            ((ExpressionLogical)nodes[LEFT]).distributeOr();
            ((ExpressionLogical)nodes[RIGHT]).distributeOr();
        }

        public override Expression getIndexableExpression(RangeVariable rangeVar)
        {

            switch (opType)
            {

                case OpTypes.IS_NULL:
                    return nodes[LEFT].opType == OpTypes.COLUMN
                           && nodes[LEFT].getRangeVariable() == rangeVar ? this
                                                                         : null;

                case OpTypes.NOT:
                    return nodes[LEFT].opType == OpTypes.IS_NULL
                           && nodes[LEFT].nodes[LEFT].opType == OpTypes.COLUMN
                           && nodes[LEFT].nodes[LEFT].getRangeVariable()
                              == rangeVar ? this
                                          : null;

                case OpTypes.EQUAL:
                    if (exprSubType == OpTypes.ANY_QUANTIFIED)
                    {
                        if (nodes[RIGHT].isCorrelated())
                        {
                            return null;
                        }

                        return nodes[LEFT].nodes[0].opType == OpTypes.COLUMN
                               && nodes[LEFT].nodes[0].getRangeVariable()
                                  == rangeVar ? this
                                              : null;
                    }
                    goto case OpTypes.SMALLER_EQUAL;
                // fall through
                case OpTypes.GREATER:
                case OpTypes.GREATER_EQUAL:
                case OpTypes.SMALLER:
                case OpTypes.SMALLER_EQUAL:
                    if (exprSubType != 0)
                    {
                        return null;
                    }

                    //                reorderComparison();
                    if (nodes[LEFT].opType == OpTypes.COLUMN
                            && nodes[LEFT].getRangeVariable() == rangeVar)
                    {
                        if (nodes[RIGHT].hasReference(rangeVar))
                        {
                            return null;
                        }

                        return this;
                    }

                    if (nodes[RIGHT].opType == OpTypes.COLUMN
                            && nodes[RIGHT].getRangeVariable() == rangeVar)
                    {
                        swapCondition();

                        if (nodes[RIGHT].hasReference(rangeVar))
                        {
                            return null;
                        }

                        return this;
                    }

                    return null;
                // fall through
                default:
                    return null;
            }
        }

        /**
         * Called only on comparison expressions after reordering which have
         * a COLUMN left leaf
         */
        public bool isSimpleBound()
        {

            if (opType == OpTypes.IS_NULL)
            {
                return true;
            }

            if (nodes[RIGHT] != null)
            {
                if (nodes[RIGHT].opType == OpTypes.VALUE)
                {

                    // also true for all parameters
                    return true;
                }

                if (nodes[RIGHT].opType == OpTypes.SQL_FUNCTION)
                {
                    if (((FunctionSQL)nodes[RIGHT]).isValueFunction())
                    {
                        return true;
                    }
                }
            }

            return false;
        }

        /**
         * Swap the condition with its complement
         */
        public void swapCondition()
        {

            int i = OpTypes.EQUAL;

            switch (opType)
            {

                case OpTypes.GREATER_EQUAL:
                    i = OpTypes.SMALLER_EQUAL;
                    break;

                case OpTypes.SMALLER_EQUAL:
                    i = OpTypes.GREATER_EQUAL;
                    break;

                case OpTypes.SMALLER:
                    i = OpTypes.GREATER;
                    break;

                case OpTypes.GREATER:
                    i = OpTypes.SMALLER;
                    break;

                case OpTypes.NOT_DISTINCT:
                    i = OpTypes.NOT_DISTINCT;
                    break;

                case OpTypes.EQUAL:
                    break;

                default:
                    throw Error.runtimeError(ErrorCode.U_S0500,
                                             "ExpressionLogical");
            }

            opType = i;

            Expression e = nodes[LEFT];

            nodes[LEFT] = nodes[RIGHT];
            nodes[RIGHT] = e;
        }

        public bool reorderComparison(Session session)
        {

            Expression colExpression = null;
            Expression nonColExpression = null;
            bool left = false;
            bool replaceColumn = false;
            int operation = 0;

            if (nodes[LEFT].opType == OpTypes.ADD)
            {
                operation = OpTypes.SUBTRACT;
                left = true;
            }
            else if (nodes[LEFT].opType == OpTypes.SUBTRACT)
            {
                operation = OpTypes.ADD;
                left = true;
            }
            else if (nodes[RIGHT].opType == OpTypes.ADD)
            {
                operation = OpTypes.SUBTRACT;
            }
            else if (nodes[RIGHT].opType == OpTypes.SUBTRACT)
            {
                operation = OpTypes.ADD;
            }

            if (operation == 0)
            {
                return false;
            }

            if (left)
            {
                if (nodes[LEFT].nodes[LEFT].opType == OpTypes.COLUMN)
                {
                    colExpression = nodes[LEFT].nodes[LEFT];
                    nonColExpression = nodes[LEFT].nodes[RIGHT];
                }
                else if (nodes[LEFT].nodes[RIGHT].opType == OpTypes.COLUMN)
                {
                    replaceColumn = operation == OpTypes.ADD;
                    colExpression = nodes[LEFT].nodes[RIGHT];
                    nonColExpression = nodes[LEFT].nodes[LEFT];
                }
            }
            else
            {
                if (nodes[RIGHT].nodes[LEFT].opType == OpTypes.COLUMN)
                {
                    colExpression = nodes[RIGHT].nodes[LEFT];
                    nonColExpression = nodes[RIGHT].nodes[RIGHT];
                }
                else if (nodes[RIGHT].nodes[RIGHT].opType == OpTypes.COLUMN)
                {
                    replaceColumn = operation == OpTypes.ADD;
                    colExpression = nodes[RIGHT].nodes[RIGHT];
                    nonColExpression = nodes[RIGHT].nodes[LEFT];
                }
            }

            if (colExpression == null)
            {
                return false;
            }

            Expression otherExpression = left ? nodes[RIGHT]
                                                        : nodes[LEFT];
            ExpressionArithmetic newArg = null;

            if (!replaceColumn)
            {
                newArg = new ExpressionArithmetic(operation, otherExpression,
                                                  nonColExpression);

                newArg.resolveTypesForArithmetic(session);
            }

            if (left)
            {
                if (replaceColumn)
                {
                    nodes[RIGHT] = colExpression;
                    nodes[LEFT].nodes[RIGHT] = otherExpression;

                    ((ExpressionArithmetic)nodes[LEFT]).resolveTypesForArithmetic(
                        session);
                }
                else
                {
                    nodes[LEFT] = colExpression;
                    nodes[RIGHT] = newArg;
                }
            }
            else
            {
                if (replaceColumn)
                {
                    nodes[LEFT] = colExpression;
                    nodes[RIGHT].nodes[RIGHT] = otherExpression;

                    ((ExpressionArithmetic)nodes[RIGHT])
                        .resolveTypesForArithmetic(session);
                }
                else
                {
                    nodes[RIGHT] = colExpression;
                    nodes[LEFT] = newArg;
                }
            }

            return true;
        }
    }
}
